Skip to content

refactor(audience): unify consent level checks via capability-query functions#2848

Merged
ImmutableJeffrey merged 10 commits into
mainfrom
refactor/audience-consent-capability-queries
Apr 14, 2026
Merged

refactor(audience): unify consent level checks via capability-query functions#2848
ImmutableJeffrey merged 10 commits into
mainfrom
refactor/audience-consent-capability-queries

Conversation

@ImmutableJeffrey

@ImmutableJeffrey ImmutableJeffrey commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add two helper functions to @imtbl/audience-core:
    • canTrack(level) — true if the level allows event recording
    • canIdentify(level) — true if the level allows identity features (identify, alias, userId, email hashing)
  • Export both from @imtbl/audience-core and @imtbl/audience
  • Replace === 'none' / === 'full' comparisons in @imtbl/audience (sdk.ts) and @imtbl/pixel (pixel.ts, autocapture.ts) with calls to these helpers
  • Retype SnippetOptions.consent from 'none' | 'anonymous' | 'full' to ConsentLevel
  • Rename Pixel's private canTrack() method to isTrackingAllowed()

LInear: SDK-122

Tests

  • core: 150 tests pass (6 new)
  • sdk: 49 tests pass
  • pixel: 78 tests pass
  • pnpm typecheck and pnpm lint clean across all three packages

@ImmutableJeffrey ImmutableJeffrey requested a review from a team as a code owner April 11, 2026 05:09
@nx-cloud

nx-cloud Bot commented Apr 11, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit e526fa0

Command Status Duration Result
nx affected -t build,lint,test ✅ Succeeded 12s View ↗
nx run-many -p @imtbl/sdk,@imtbl/checkout-widge... ✅ Succeeded 8s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-13 23:24:17 UTC

@github-actions

github-actions Bot commented Apr 11, 2026

Copy link
Copy Markdown

✅ Pixel Bundle Size — @imtbl/pixel

Metric Size Delta vs main
Gzipped 5435 bytes (5.30 KB) +23 bytes
Raw (minified) 14744 bytes +71 bytes

Budget: 10.00 KB gzipped (warn at 8.00 KB)

Comment thread packages/audience/sdk/src/sdk.ts Outdated
Comment thread packages/audience/pixel/src/pixel.ts Outdated
ImmutableJeffrey and others added 9 commits April 14, 2026 09:05
Add canTrack(), canIdentify(), and includesUserId() to consent.ts as
the single source of truth for what each consent level permits.

Currently the behavioral rules (can I track at this level? can I
identify? should I attach userId?) are expressed as raw string
comparisons scattered across 21+ call sites in sdk, pixel, and
autocapture. If consent levels or rules change, every call site
needs updating.

These three functions centralise the rules:
- canTrack(level)       — true for anonymous + full
- canIdentify(level)    — true for full only
- includesUserId(level) — true for full only

Subsequent commits will replace the scattered comparisons with calls
to these functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 6 scattered consent string comparisons in sdk.ts with calls
to canTrack(), canIdentify(), and includesUserId() from core:

- constructor: consentLevel !== 'none' → canTrack(consentLevel)
- isTrackingDisabled(): === 'none' → !canTrack(level)
- effectiveUserId(): === 'full' → includesUserId(level)
- identify(): !== 'full' → !canIdentify(level)
- alias(): !== 'full' → !canIdentify(level)
- setConsent upgrade detection: string comparison → canTrack()

setConsent() transition-specific checks (→none stops queue, clears
cookies; →anonymous from full clears userId) remain as direct
comparisons — they're state transitions, not capability queries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 5 consent string comparisons in pixel.ts with calls to
consentAllowsTracking() from core:

- init(): level !== 'none' → consentAllowsTracking(level)
- setConsent(): level !== 'none' → consentAllowsTracking(level)
- startCmpDetection() onUpdate: level !== 'none' → consentAllowsTracking
- startCmpDetection() onDetected: detector.level !== 'none' → same
- canTrack(): level !== 'none' → consentAllowsTracking(level)

The earlier version of pixel.ts had userId/identify checks that needed
includesUserId() and canIdentify(), but those were removed in PR #2846
(pixel identify removal). Only canTrack() is needed now.

Update pixel.test.ts mock to include the canTrack export.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ons type

autocapture.ts — replace 3 consent string comparisons:
- form submit guard: getConsent() === 'none' → !canTrack(getConsent())
- email hash gate: consent === 'full' → includesUserId(consent)
- link click guard: getConsent() === 'none' → !canTrack(getConsent())

snippet.ts — replace inline literal union 'none' | 'anonymous' | 'full'
with the ConsentLevel type from @imtbl/audience-core. This was the one
place consent values were defined outside the single source of truth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tity gates

canIdentify() and includesUserId() had identical implementations
(level === 'full'). Collapse to just canIdentify() — the name covers
both "can I call identify/alias" and "should I attach userId or hash
email", which are the same gate.

Two functions is the right number:
- canTrack(level)    — can this level record events?
- canIdentify(level) — can this level use identity features?

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consumer apps (e.g. Play) import from @imtbl/audience, not
@imtbl/audience-core. Re-export the two consent capability functions
so consumers can use them without adding a direct core dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert the 3 remaining raw consent string comparisons in sdk.ts
setConsent() to use the capability functions:

- level === 'none' (queue stop)    → !canTrack(level)
- level === 'none' (cookie delete) → !canTrack(level)
- level === 'anonymous' && previous === 'full' (userId clear)
  → canIdentify(previous) && !canIdentify(level)

These were left as raw comparisons in the initial refactor because
they're state transitions, but they gate the same capabilities as the
query functions. If a future consent level blocks tracking (like
'none' does), these would silently diverge without this change.

Also fix canTrack mock in pixel.test.ts from a bare arrow to
jest.fn().mockImplementation() so it can be overridden per-test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment referenced the 'none' level specifically, but the check now
delegates to canTrack(), which is the capability source of truth.
Describe the predicate in the same terms so the comment doesn't rot
if another non-tracking level is added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous commit aliased the imported canTrack to avoid colliding
with pixel's pre-existing private method of the same name. Invert
that choice: rename the private method, drop the import alias. The
imported canTrack now reads the same as in autocapture.ts and every
other call site, and isTrackingAllowed better describes the private
method (it also checks isReady, not just consent).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ImmutableJeffrey ImmutableJeffrey force-pushed the refactor/audience-consent-capability-queries branch from 8d86957 to 769986e Compare April 13, 2026 23:08
The section header and doc comments restated what the function names
already convey. Removed per the project's preference to comment only
when the reason is non-obvious.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ImmutableJeffrey ImmutableJeffrey added this pull request to the merge queue Apr 14, 2026
Merged via the queue into main with commit e98d48e Apr 14, 2026
14 checks passed
@ImmutableJeffrey ImmutableJeffrey deleted the refactor/audience-consent-capability-queries branch April 14, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants